//"Subclasses" jquery.autocomplete such that the autocomplete suggestions box is attached to the parent.
//Unfortunately there is no useful way to override jquery.autocomplete's private variables, so we have to use a custom version of jquery.autocomplete.
//The customisation is on lines 53 and 81 of jquery.autocomplete.js, to allow a different "Select" widget to be used.


;(function($) {

//I. Some subclassing machinery, adapted from
//http://www.lshift.net/blog/2006/08/03/subclassing-in-javascript-part-2
$.extend({
    general_subclass: function(superclass, constructor, prototype) {
        var withoutcon = function () {};
        withoutcon.prototype = superclass.prototype;
        constructor.prototype = new withoutcon();
        for (var k in prototype) {
            constructor.prototype[k] = prototype[k];
        }
        return constructor;
    },
    subclass: function(superclass, constructor_extend, prototype) { //Use this if you don't need to pass parameters to the superclass's constructor.
        return $.general_subclass(superclass, function () {
            superclass.apply(this);
            constructor_extend.apply(this, arguments);
        }, prototype);
    }
});

    
//II. The subclasses/tweaks
$.fn.extend({ 
	parentautocomplete: function(urlOrData, options) { //Copied verbatim from autocomplete, modified to create a ParentAutocompleter.
		var isUrl = typeof urlOrData == "string";
		options = $.extend({}, $.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
			max: options && !options.scroll ? 10 : 150
		}, options);
		
		// if highlight is set to false, replace it with a do-nothing function
		options.highlight = options.highlight || function(value) { return value; };
		
		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
		options.formatMatch = options.formatMatch || options.formatItem;
		
		return this.each(function() {
			new $.ParentAutocompleter(this, options);
		});
	}
});

$.ParentAutocompleter = (function (uber) { //A subclass of Autocompleter. The original has been hacked to allow override of 'select'.
    return $.general_subclass(uber, function(input, options) {
        uber.apply(this, [input, options, $.ParentAutocompleter.ParentSelect]); //Call the super constructor
    }, {
        //Nothing in the prototype
    });
})($.Autocompleter);


//III.The parentselect subclass (I think the original plugin should be refactored to allow subclassing)
$.ParentAutocompleter.ParentSelect = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};

	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;

	// Create results
	function init() {
		if (!needsInit)
			return;
		element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);

		list = $("<ul/>").appendTo(element).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
            select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
            // GT MOD: now do the blur.  Even by the time it's got here, the textbox's onblur event has triggered
			$(input).attr("noblur","false");
            input.blur(); 
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
			//GT mod:
			$(input).attr("noblur","true"); //Prevents the normal "onblur" effect of creating a token, because we've clicked on an item that will do it anyway.
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});

		if( options.width > 0 )
			element.css("width", options.width);

		$(input).bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
    		lastKeyPressCode = event.keyCode;
    		switch(event.keyCode) {
    			case 9: //KEY.TAB:
    			case 13: //KEY.RETURN:
                    input.blur();
    			break;
    		}
        });

		needsInit = false;
	} 

	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {
		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {
                list.scrollTop(offset);
            }
        }
	};

	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}

	function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}

	function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
		for (var i=0; i < max; i++) {
			if (!data[i])
				continue;
			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
			if ( formatted === false )
				continue;
			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
			$.data(li, "ac_data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}

	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {
				moveSelect(-8);
			}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {
				moveSelect(8);
			}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE);
			active = -1;
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() { //GT mod
			var offset = $(input).parent().offset();
			element.css({
				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).parent().width(), //GT
				top: offset.top + $(input).parent().outerHeight(false),
				left: offset.left
			}).show();
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
					maxHeight: options.scrollHeight,
					overflow: 'auto'
				});

                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }

            }
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ac_data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

})(jQuery);